# -*- encoding: utf-8 -*-
############################################################################
#    Copyright (C) 2007 by Håvard Gulldahl                                 #
#    havard@gulldahl.no                                                    #
#                                                                          #
#    This program is free software; you can redistribute it and#or modify  #
#    it under the terms of the GNU General Public License as published by  #
#    the Free Software Foundation; either version 2 of the License, or     #
#    (at your option) any later version.                                   #
#                                                                          #
#    This program is distributed in the hope that it will be useful,       #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of        #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         #
#    GNU General Public License for more details.                          #
#                                                                          #
#    You should have received a copy of the GNU General Public License     #
#    along with this program; if not, write to the                         #
#    Free Software Foundation, Inc.,                                       #
#    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             #
############################################################################

import sys, types, re, logging, time, datetime, urlparse, cPickle as pickle
from string import join
from inspect import ismethod, isfunction
import BeautifulSoup, storm.exceptions as storm_exceptions, storm.locals as storm
import parsers

__author__ = u'Håvard Gulldahl <havard@gulldahl.no>'

__all__ = ['collector', 'parser', 'parsers', 'fingerprinter', 'lookup',
           'user']

_store = None

class ejejeError(Exception):
  pass

class ejeje_storm(storm.Storm):
    pass
    
class ejeje_postjournal(ejeje_storm):
    __storm_table__ = 'postjournal'
    _sql = """CREATE TABLE postjournal (
    id MEDIUMINT UNSIGNED auto_increment,
    uri VARCHAR(255),
    format VARCHAR(20),
    organisasjon_id MEDIUMINT UNSIGNED,
    enkeltjournal_uri VARCHAR(255),
    bestill_uri VARCHAR(255),
    fungerer BOOLEAN DEFAULT 1,
    fjernet BOOLEAN DEFAULT 0,
    stanset BOOLEAN DEFAULT 0,
    kommentar VARCHAR(255),
    etterslep TINYINT UNSIGNED,
    implisitt_enkeltjournal BOOLEAN DEFAULT 0,
    PRIMARY KEY(id));
    """
    id = storm.Int(primary=True)
    uri = storm.Unicode()
    organisasjon_id = storm.Int()
    organisasjon = storm.Reference(organisasjon_id, 'ejeje_organisasjon.id')
    enkeltjournal_uri = storm.Unicode()
    implisitt_enkeltjournal = storm.Bool(default=True)
    bestill_uri = storm.Unicode()
    fungerer = storm.Bool(default=True)
    fjernet = storm.Bool(default=False)
    stanset = storm.Bool(default=False)
    etterslep = storm.Int(default=2) ## etterslep i journalføringen, i dager
    kommentar = storm.Unicode()
    format = storm.Unicode(default=u'')

    def ny_enkeltjournal(self, uri):
        enkeltjournal = ejeje_enkeltjournal()
        enkeltjournal.uri = uri
        enkeltjournal.postjournal = self
        return _store.add(enkeltjournal)

    def sett_organisasjon(self, organisasjon):
        if isinstance(organisasjon, ejeje_organisasjon):
            self.organisasjon = organisasjon
        else:
            _organisasjon = finn_organisasjon(organisasjon, fuzzy=False)
            if _organisasjon is None: # unknown organisasjonanization
                _organisasjon = ejeje_organisasjon(unicode(organisasjon))
            self.organisasjon = _organisasjon
        return self.organisasjon

class ejeje_postjournal_logg(ejeje_storm):
    __storm_table__ = 'postjournal_logg'
    _sql = """CREATE TABLE postjournal_logg (
    id MEDIUMINT UNSIGNED auto_increment,
    postjournal_id MEDIUMINT UNSIGNED,
    tidspunkt TIMESTAMP,
    journaldato VARCHAR(30),
    req_uri VARCHAR(255),
    req_status VARCHAR(50),
    req_details VARCHAR(256),
    ferdig BOOLEAN DEFAULT 0,
    PRIMARY KEY(id));
    """
    id = storm.Int(primary=True)
    postjournal_id = storm.Int()
    postjournal = storm.Reference(postjournal_id, ejeje_postjournal.id)
    tidspunkt = storm.DateTime()
    journaldato = storm.Date()
    req_uri = storm.Unicode()
    req_status = storm.Unicode()
    req_details = storm.Unicode()
    ferdig = storm.Bool(default=False)

class ejeje_enkeltjournal(ejeje_storm):
    __storm_table__ = 'enkeltjournal'
    _sql = """CREATE TABLE enkeltjournal (
    id MEDIUMINT UNSIGNED auto_increment,
    uri VARCHAR(255) NOT NULL,
    postjournal_id INT UNSIGNED,
    dato DATE,
    PRIMARY KEY(id));
    """
    id = storm.Int(primary=True)
    uri = storm.Unicode()
    postjournal_id = storm.Int()
    postjournal = storm.Reference(postjournal_id, ejeje_postjournal.id)
    dato = storm.Date()

    def ny_post(self):
        post = ejeje_journalpost()
        post.enkeltjournal = self
        post.postjournal_id = self.postjournal_id
        return _store.add(post)
        #return post


class ejeje_uri(object):
    def __init__(self, u):
        self.uri = u

    def parse(self):
        return urlparse.urlparse(self.uri)

class ejeje_adressat(ejeje_storm):
    __storm_table__ = 'adressat'
    _sql = """CREATE TABLE adressat (
    id MEDIUMINT UNSIGNED auto_increment,
    navn VARCHAR(255) NOT NULL,
    uri VARCHAR(255),
    PRIMARY KEY(id));
    """
    id = storm.Int(primary=True)
    navn = storm.Unicode()
    uri = storm.Unicode()

def finn_adressat(adressat, fuzzy=False):
    if fuzzy:
        adressat = '%'+adressat+'%'
    try:
        return _store.find(ejeje_adressat, ejeje_adressat.navn.like(unicode(adressat))).one()
    except storm_exceptions.NotOneError: 
        return None


class ejeje_organisasjon(ejeje_storm):
    TYPER = {'kommune':1, 'avdeling':2, 'fylke':3, 'etat':4}
    #TYPER = {1:'kommune', 2:'avdeling', 3:'fylke', 4:'etat'}
    __storm_table__ = 'organisasjon'
    _sql = """CREATE TABLE organisasjon (
    id MEDIUMINT UNSIGNED auto_increment,
    navn VARCHAR(128) NOT NULL,
    moder_id MEDIUMINT UNSIGNED,
    type TINYINT UNSIGNED,
    uri VARCHAR(255), 
    PRIMARY KEY(id));
    """
    id = storm.Int(primary=True)
    navn = storm.Unicode()
    moder_id = storm.Int()
    moder = storm.Reference(moder_id, 'ejeje_organisasjon.id')
    type = storm.Enum(map=TYPER)
    #type = storm.Unicode(default=u'kommune')
    uri = storm.Unicode()#Unicode()

def finn_organisasjon(org, fuzzy=False):
    if fuzzy:
        org = '%'+org+'%'
    try:
        return _store.find(ejeje_organisasjon, ejeje_organisasjon.navn.like(unicode(org))).one()
    except storm_exceptions.NotOneError: 
        return None

class ejeje_saksbehandler(ejeje_storm):
    __storm_table__ = 'saksbehandler'
    _sql = """CREATE TABLE saksbehandler (
    id MEDIUMINT UNSIGNED auto_increment,
    navn VARCHAR(128) NOT NULL,
    initialer VARCHAR(10),
    epost VARCHAR(128),
    uri VARCHAR(255),
    organisasjon_id MEDIUMINT UNSIGNED,
    PRIMARY KEY(id));
    """
    id = storm.Int(primary=True)
    navn = storm.Unicode(default=u'')
    initialer = storm.Unicode(default=u'')
    epost = storm.Unicode()
    telefon = storm.Unicode()
    uri = storm.Unicode()#Unicode()
    organisasjon_id = storm.Int()
    organisasjon = storm.Reference(organisasjon_id, ejeje_organisasjon.id)

    def __init__(self, navn):
        self.navn = unicode(navn)
        self.initialer = join([b[0] for b in self.navn.split()], u'')
    def sett_organisasjon(self, org):
        if isinstance(org, ejeje_organisasjon):
            self.organisasjon = org
        else:
            _org = finn_organisasjon(org, fuzzy=False)
            if _org is None: # unknown organization
                _org = ejeje_organisasjon()
                _org.navn = unicode(org)
            self.organisasjon = _org
        return self.organisasjon

def finn_saksbehandler(navn, fuzzy=False):
    if fuzzy:
        navn = '%'+navn+'%'
    try:
        return _store.find(ejeje_saksbehandler, ejeje_saksbehandler.navn.like(unicode(navn))).one()
    except storm_exceptions.NotOneError: 
        return None

class ejeje_journalpost(ejeje_storm):
    __storm_table__ = 'journalpost'
    _sql = """CREATE TABLE journalpost (
    id INTEGER UNSIGNED auto_increment,
    tittel VARCHAR(255) NOT NULL,
    arkivsaksnr VARCHAR(25),
    dokumentnr VARCHAR(25),
    refnr VARCHAR(25),
    adressat_id INTEGER UNSIGNED,
    saksbehandler_id INTEGER UNSIGNED,
    dokumentdato DATETIME,
    arkivdato  DATETIME,
    offentlig BOOLEAN DEFAULT 1,
    uri  VARCHAR(255),
    lovreferanse  VARCHAR(30),
    retning VARCHAR(3),
    sakskompleks VARCHAR(90),
    postjournal_id MEDIUMINT UNSIGNED,
    enkeltjournal_id MEDIUMINT UNSIGNED,
    organisasjon_id MEDIUMINT UNSIGNED,
    kommentar  VARCHAR(255),
    PRIMARY KEY(id));
    """
    id = storm.Int(primary=True)
    tittel = storm.Unicode()
    arkivsaksnr = storm.Unicode()
    dokumentnr = storm.Unicode()
    refnr = storm.Unicode() ## løpenummer
    adressat_id = storm.Int()
    adressat = storm.Reference(adressat_id, ejeje_adressat.id) # avsender/mottaker
    saksbehandler_id = storm.Int()
    saksbehandler = storm.Reference(saksbehandler_id, ejeje_saksbehandler.id)
    dokumentdato = storm.Date() # datetime.datetime or datetime.date instance ( or %Y-%m-%d ?)
    arkivdato = storm.Date()
    offentlig = storm.Bool(default=True)
    uri = storm.Unicode()
    dok_uri = storm.Unicode()
    lovreferanse = storm.Unicode()
    RETNINGER = {'I':1, 'U':2, 'N':3, 'S':4, 'X':0}
    retning = storm.Enum(map=RETNINGER, default='X') # incoming / outgoing / unknown(?)
    #retning = storm.Enum(map={1:'I', 2:'U', 3:'N', 4:'S', 5:'X'}, default='X') # incoming / outgoing / unknown(?)
    #retning = storm.Unicode(default=u'X')#Enum(map={1:'I', 2:'U', 3:'N', 4:'S', 5:'X'}, default='X') # incoming / outgoing / unknown(?)
    # I: Inngående
    # U: Utgående
    # N: ??
    # S: Saksfremlegg/innstilling
    # X: ??
    sakskompleks = storm.Unicode()
    postjournal_id = storm.Int()
    postjournal = storm.Reference(postjournal_id, ejeje_postjournal.id)
#    organisasjon_id = storm.Int()
#    organisasjon = storm.Reference(organisasjon_id, ejeje_organisasjon.id)
    enkeltjournal_id = storm.Int()
    enkeltjournal = storm.Reference(enkeltjournal_id, ejeje_enkeltjournal.id)
    kommentar = storm.Unicode()



    def sett_saksbehandler(self, saksbehandler):
        if isinstance(saksbehandler, ejeje_saksbehandler):
            self.saksbehandler = saksbehandler
        else:
            _saksbehandler = finn_saksbehandler(saksbehandler, fuzzy=False)
            if _saksbehandler is None: # unknown saksbehandleranization
                _saksbehandler = ejeje_saksbehandler(navn=unicode(saksbehandler))
            self.saksbehandler = _saksbehandler
        return self.saksbehandler

    def sett_adressat(self, adressat):
        if isinstance(adressat, ejeje_adressat):
            self.adressat = adressat
        else:
            _adressat = finn_adressat(adressat, fuzzy=False)
            if _adressat is None: # unknown adressatanization
                _adressat = ejeje_adressat()
                _adressat.navn=unicode(adressat)
            self.adressat = _adressat
        return self.adressat
    def sjekk_streng(self, s):
        if isinstance(s, basestring):
            if s.strip() in ('None', ):
                raise ejejeError("Mottok en streng med ugyldig innhold: '%s'" % s)
            return s.strip().replace('\r\n', '').replace('\n', '')
        else:
            raise ejejeError("Forventet streng, fikk '%s' (%s)" % (s, type(s)))
    def sjekk_bool(self, b):
        if isinstance(b, bool):
            return b
        else:
            raise ejejeError("Forventet bool, fikk '%s' (%s)" % (b, type(b)))
    def sjekk_dato(self, dato):
        if isinstance(dato, datetime.datetime):
            return dato
        else:
            raise ejejeError("Forventet datetime.datetime, fikk '%s' (%s)" % (dato, type(dato)))
    def sjekk_uri(self, s):
        uri = self.sjekk_streng(s).replace(" ", "")
        if not (uri.startswith('http://') or uri.startswith('https://')):
            raise ejejeError("Relativ uri: %s" % uri)
        return uri
    def sett_tittel(self, tittel):
        self.tittel = self.sjekk_streng(tittel)
    def sett_arkivsaksnr(self, nr):
        self.arkivsaksnr = self.sjekk_streng(nr)
    def sett_dokumentnr(self, nr):
        self.dokumentnr = self.sjekk_streng(nr)
    def sett_refnr(self, nr):
        self.refnr = self.sjekk_streng(nr)
    def sett_uri(self, u):
        self.uri = self.sjekk_uri(u)
    def sett_dok_uri(self, u):
        self.dok_uri = self.sjekk_uri(u)
    def sett_lovreferanse(self, ref):
        self.lovreferanse = self.sjekk_streng(ref)
    def sett_sakskompleks(self, s):
        self.sakskompleks = self.sjekk_streng(s)
    def sett_kommentar(self, s):
        self.kommentar = self.sjekk_streng(s)
    def sett_offentlig(self, b):
        self.offentlig = self.sjekk_bool(b)
    def sett_retning(self, retning):
        r = self.sjekk_streng(retning)
        if r in self.RETNINGER.keys():
            self.retning = r
        else:
            raise ejejeError("Ukjent retning: '%s'" % retning)
    def sett_dokumentdato(self, d):
        self.dokumentdato = self.sjekk_dato(d)
    def sett_arkivdato(self, d):
        self.arkivdato = self.sjekk_dato(d)
    def kontroller_felter(self):
        logging.info("Kontrollerer joournalpost #%s", self.id)
        if not self.tittel:
            logging.error("Journalpost #%s har ingen tittel. Sjekk parseren", self.id)
            raise ejejeError("Journalpost #%s har ingen tittel. Sjekk parseren", self.id)
        if self.tittel.strip() in ("None", ""):
            logging.error("Journalpost #%s har en ugyldig tittel: '%s'. Sjekk parseren", self.id, self.tittel)
            raise ejejeError("Journalpost #%s har en ugyldig tittel: '%s'. Sjekk parseren", self.id, self.tittel)
        if not self.adressat and self.offentlig == True:
            logging.warn("Journalpost #%s har ingen adressat. Sjekk parseren", self.id)
        if not (self.dokumentdato or self.arkivdato):
            logging.warn("Journalpost #%s har ingen fornuftig dato satt. Sjekk parsern", self.id)
            self.arkivdato = self.enkeltjournal.dato
        if self.offentlig is None:
            logging.warn("Journalpost #%s har ugyldig offentlighetsverdi. Sjekk parsern", self.id)
            self.offentlig = True
        if self.offentlig == False and self.dok_uri is not None:
            logging.warn("Journalpost #%s er markert som ikke offentlig, men har dokument-uri. Sjekk parsern", self.id)
        if self.uri and not (self.uri.startswith('http://') or self.uri.startswith('https://')):
            logging.warn("Journalpost #%s har en uri som ser relativ ut: '%s'", self.id, self.uri)
        if self.dok_uri and not (self.dok_uri.startswith('http://') or self.dok_uri.startswith('https://')):
            logging.warn("Journalpost #%s har en uri som ser relativ ut: '%s'", self.id, self.dok_uri)
        

class ejeje_feil(ejeje_storm):
    __storm_table__ = 'feil'
    _sql = """CREATE TABLE feil (
    id MEDIUMINT UNSIGNED auto_increment,
    tidspunkt TIMESTAMP,
    uri VARCHAR(255),
    html TEXT,
    suppe TEXT,
    detaljer TEXT,
    postjournal_id INTEGER UNSIGNED,
    PRIMARY KEY(id));
    """
    id = storm.Int(primary=True)
    tidspunkt = storm.Date()
    uri = storm.Unicode()#Unicode()
    html = storm.Unicode()
    suppe = storm.Unicode()
    detaljer = storm.Unicode()
    postjournal_id = storm.Int()
    postjournal = storm.Reference(postjournal_id, ejeje_postjournal.id)

    def __init__(self, exception):
        self.tidspunkt = datetime.datetime.now()
        self.detaljer = unicode(repr(exception))


